<template>
    <div class="map-container" id="map-container" @click="popIds = []">
        <div v-if="!mapData.length" class="no-data-container flex-column flex-center">
            <img src="./img/no-data.png" alt="">
            <p class="tip">暂无数据</p>
        </div>
        <template v-for="list in nodeLevels">
            <div class="node-item flex-column flex-center"
                 v-for="node in list"
                 :key="node.id"
                 v-drag="(res) => handDrag(res, node)"
                 @contextmenu="(e) => openPop(e, node.id)"
                 :style="{
                  left: node.x + 'px',
                  top: node.y + 'px',
                  width: (sizes.diameter + 10) + 'px'
                }">
                <node-info :popIds="popIds"
                           :ref="`pop${node.id}`"
                           :node="node"
                           @editNode="editNode"
                           @deleteNode="deleteNode"
                           :sizes="sizes"/>
            </div>
        </template>
        <div id="line-container" class="flex-row flex-item">
            <div class="flex-row path-line flex-item"
                 v-for="(item, index) in allLines"
                 :key="`${item.node.id}-${item.child.id}`"
                 :id="`line-${item.node.id}-${item.child.id}`">
                <div @click.stop="() => {}" class="bar start-bar" @mousedown="e => endBarMousedown(e, index, 'start')"></div>
                <div class="flex1"></div>
                <i @click.stop="deleteLine(item)" class="el-icon-error"></i>
                <div class="flex1"></div>
                <div @click.stop="() => {}" class="bar" @mousedown="e => endBarMousedown(e, index, 'end')"></div>
            </div>
        </div>
    </div>
</template>

<script>
// import {uuid} from '@/utils/uuid';
import nodeInfo from './nodeInfo'

export default {
  name: 'topologyMap',
  components: { nodeInfo },
  data() {
    return {
      lineConfig: {
        width: 1,
        color: '#31C4BA'
      },
      sizes: {
        diameter: 60,
        spacingX: 100,
        spacingY: 100
      },
      mapData: [],
      // 按照拓扑关系分层后的数据
      nodeLevels: [],
      // 展示的数据
      dispalyData: [],
      // 当前拖拽的id
      targetId: '',
      timer: null,
      // 线条端点
      linePoints: [],
      waitAddLine: [],
      popIds: []
    }
  },
  methods: {
    pageInit(mapData) {
      this.waitAddLine = []
      if (mapData) {
        this.mapData = mapData.map(item => {
          return {
            ...item,
            children: item.children || []
          }
        })
      }
      // 找出所有子节点
      const importIds = this.mapData.map(item => {
        return item.children.map(node => node.id)
      }).flat().filter(id => id !== '3f5c17be71660b93989427dc55b09f3e')
      // 找到根节点
      const rootNode = this.mapData.filter(item => {
        return !importIds.includes(item.id)
      })
      const findChildrenIds = (nodes) => {
        return nodes.map(item => {
          return item.children
        }).flat().map(item => item.id)
      }
      this.nodeLevels = [rootNode]
      const loop = (list) => {
        const nextIds = findChildrenIds(list)
        const currentIds = this.nodeLevels.flat().map(item => item.id)
        const nextNodes = this.mapData.filter(item => nextIds.includes(item.id) && !currentIds.includes(item.id))
        this.nodeLevels.push(nextNodes)
        if (nextIds.length) {
          loop(nextNodes)
        }
      }
      loop(rootNode)
      this.initCoordinate()
      this.$nextTick(() => {
        this.connectNode()
      })
    },
    openPop(event, id) {
      event.preventDefault()
      this.popIds = [id]
    },
    editNode(node) {
      this.$emit('editNode', node)
    },
    deleteNode(node) {
      this.$emit('deleteNode', node)
    },
    deleteLine(item) {
      const { node, child } = item
      if (item.isNew) {
        this.waitAddLine = []
      } else {
        this.$confirm(`是否删除${node.nodeName}与${child.nodeName}之间的连接？`).then(() => {
          this.editLine(node, child, true)
          this.pageInit()
        // }).catch(err => {
        }).catch(() => {
          console.log('取消删除')
        })
      }
    },
    addItem(item, e) {
      const dom = document.querySelector('#map-container')
      const { top, left } = dom.getBoundingClientRect()
      const { clientX, clientY } = e
      if (item.value === 'line') {
        const start = {
          x: clientX + 8 - left,
          y: clientY - top
        }
        const end = {
          x: start.x + 100,
          y: start.y
        }
        const node = {id: 'addstart'}
        const child = {id: 'addend'}
        const newNode = {start, end, node, child, isNew: true}
        this.waitAddLine = [newNode]
        this.setLineStyle(newNode)
      } else {
        this.$emit('addNode', {
          code: '',
          ip: '',
          nodeMac: '',
          vlan: '',
          nodeName: item.keyName + '—节点',
          deviceType: item.deviceType,
          deviceTypeTwo: item.deviceTypeTwo,
          x: clientX + 8 - left - this.sizes.diameter / 2,
          y: clientY - top - this.sizes.diameter / 2,
          children: []
        })
      }
    },
    endBarMousedown(e, index, type) {
      const dom = document.querySelector('#map-container')
      const { top, left } = dom.getBoundingClientRect()
      const item = this.allLines[index]
      const handleMove = (e) => {
        const { clientX, clientY } = e
        item[type].x = clientX + 8 - left
        item[type].y = clientY - top
        this.setLineStyle(item)
      }
      const handleUp = () => {
        document.removeEventListener('mouseup', handleUp)
        document.removeEventListener('mousemove', handleMove)
        this.countTarget(item, type)
      }
      // const handleMove = (e) => {
      //   const { clientX, clientY } = e
      //   item[type].x = clientX + 8 - left
      //   item[type].y = clientY - top
      //   this.setLineStyle(item)
      // }
      document.addEventListener('mouseup', handleUp)
      document.addEventListener('mousemove', handleMove)
    },
    addNodeRelation(nodeId, importNodeId) {
      return this.$http.post('/topu/addNodeRelation', {
        importNodeId,
        nodeId
      })
    },
    deleteNodeRelation(node, child) {
      let relationId = ''
      const parent = this.mapData.find(item => item.id === node.id)
      if (parent?.children) {
        relationId = parent.children.find(item => item.id === child.id).relationId
      }
      return this.$http.delete(`/topu/deleteNodeRelation?id=${relationId}`)
    },
    editLine(node, child, isDelete) {
      if (isDelete) {
        // this.deleteNodeRelation(node, child).then(res => {
        this.deleteNodeRelation(node, child).then(() => {
          this.refresh()
        })
      } else {
        const parentNode = this.mapData.find(item => item.id === node.id)
        const childNode = this.mapData.find(item => item.id === child.id)
        if (parentNode.children.find(item => item.id === child.id) ||
                        childNode.children.find(item => item.id === node.id)) {
          // 原来已有连接关系，不再添加
          return
        }
        // this.addNodeRelation(node.id, child.id).then(res => {
        this.addNodeRelation(node.id, child.id).then(() => {
          this.refresh()
        }).catch(err => {
          this.$TZmessage.error(err.msg || '操作失败')
        })
      }
    },
    refresh() {
      if (this.timer) {
        clearTimeout(this.timer)
        this.timer = null
      }
      this.timer = setTimeout(() => {
        this.$emit('refresh')
        this.timer = null
      }, 50)
    },
    // countTarget(cur, type) {
    //   const tx = cur[type].x
    //   const ty = cur[type].y
    //   let targetNode = null
    //   for (let nodes of this.nodeLevels) {
    //     for (let node of nodes) {
    //       const cx = node.x + (this.sizes.diameter + 10) / 2
    //       const cy = node.y + (this.sizes.diameter + 10) / 2
    //       if (Math.sqrt((cx - tx) ** 2 + (cy - ty) ** 2) < 35) {
    //         targetNode = node
    //       }
    //     }
    //   }
    //   const { node, child } = cur
    //   if ([node?.id, child?.id].includes(targetNode?.id)) {
    //     this.$emit('refresh')
    //     return
    //   }
    //   // 待添加连线逻辑
    //   if (cur.isNew && targetNode) {
    //     if (type === 'end') {
    //       cur.child = targetNode
    //     } else {
    //       cur.node = targetNode
    //     }
    //     const { node, child } = cur
    //     if (node.id !== 'addstart' && child.id !== 'addend') {
    //       this.editLine(node, child)
    //       this.waitAddLine = []
    //     }
    //     this.setLineStyle(cur)
    //   } else if (targetNode) {
    //     if (type === 'end') {
    //       this.editLine(node, child, true)
    //       this.editLine(node, targetNode)
    //     } else {
    //       this.editLine(node, child, true)
    //       this.editLine(targetNode, child)
    //     }
    //   }
    // },
    countTarget(cur, type) {
      const tx = cur[type].x
      const ty = cur[type].y
      let targetNode = null
      for (let nodes of this.nodeLevels) {
        for (let node of nodes) {
          const cx = node.x + (this.sizes.diameter + 10) / 2
          const cy = node.y + (this.sizes.diameter + 10) / 2
          if (Math.sqrt((cx - tx) ** 2 + (cy - ty) ** 2) < 35) {
            targetNode = node
          }
        }
      }
      const { node, child } = cur
      if ([node?.id, child?.id].includes(targetNode?.id)) {
        this.$emit('refresh')
        return
      }
      // 待添加连线逻辑
      (() => {
        if (cur.isNew && targetNode) {
          type === 'end' ? cur.child = targetNode : cur.node = targetNode
          const { node, child } = cur
          if (node.id !== 'addstart' && child.id !== 'addend') {
            this.editLine(node, child)
            this.waitAddLine = []
          }
          this.setLineStyle(cur)
        } else if (targetNode) {
          this.editLine(node, child, true)
          type === 'end' ? this.editLine(node, targetNode) : this.editLine(targetNode, child)
          // if (type === 'end') {
          //   this.editLine(node, child, true)
          //   this.editLine(node, targetNode)
          // } else {
          //   this.editLine(node, child, true)
          //   this.editLine(targetNode, child)
          // }
        }
      })()
    },
    handDrag(data, node) {
      const { left, top, status } = data
      if (status === 'draging') {
        for (let level = 0; level < this.nodeLevels.length; level++) {
          const list = this.nodeLevels[level]
          for (let i = 0; i < list.length; i++) {
            if (list[i].id === node.id) {
              list[i].x = left
              list[i].y = top
              break
            }
          }
        }
        this.mapData = this.mapData.map(item => {
          if (item.id === node.id) {
            item.x = left
            item.y = top
          }
          return item
        })
        this.nodeLevels = [...this.nodeLevels]
        this.connectNode()
      } else if (status === 'dragend' && data.el.button === 0) {
        this.$emit('moveNode', node)
      }
    },
    initCoordinate() {
      this.nodeLevels = this.nodeLevels.map((list, level) => {
        const { diameter, spacingX, spacingY } = this.sizes
        return list.map((item, index) => {
          const x = item.x === null ? (spacingX + diameter) * level : item.x
          const y = item.y === null ? (diameter + spacingY) * index : item.y
          return {
            ...item,
            x,
            y
          }
        })
      })
    },
    connectNode() {
      let { diameter } = this.sizes
      diameter += 10
      const r = diameter / 2
      const nodes = this.nodeLevels.flat()
      this.linePoints = []
      nodes.forEach(node => {
        if (node.children?.length) {
          const ids = node.children.map(item => item.id)
          const childrenNodes = nodes.filter(item => ids.includes(item.id))
          childrenNodes.forEach(child => {
            const startC = {
              x: node.x + r,
              y: node.y + r
            }
            const endC = {
              x: child.x + r,
              y: child.y + r
            }
            const start = {
              x: 0,
              y: 0
            }
            const end = {
              x: 0,
              y: 0
            }
            if (endC.x !== startC.x) {
              const angle = Math.atan((endC.y - startC.y) / (endC.x - startC.x)) * -1
              const difX = Math.cos(angle) * r
              const difY = Math.sin(angle) * r
              if (startC.x < endC.x) {
                start.x = startC.x + difX
                end.x = endC.x - difX
                start.y = startC.y - difY
                end.y = endC.y + difY
              } else {
                start.x = startC.x - difX
                end.x = endC.x + difX
                start.y = startC.y + difY
                end.y = endC.y - difY
              }
            } else {
              start.x = startC.x
              end.x = endC.x
              if (startC.y > endC.y) {
                start.y = startC.y - r
                end.y = endC.y + r
              } else {
                start.y = startC.y + r
                end.y = endC.y - r
              }
            }
            this.linePoints.push({start, end, node, child})
          })
        }
      })
      this.linePoints.forEach(item => {
        this.setLineStyle(item)
      })
    },
    setLineStyle(item) {
      this.$nextTick(() => {
        const {start, end, node, child} = item
        let angle = Math.PI / 2
        angle = Math.atan((end.y - start.y) / (end.x - start.x))
        if (start.x > end.x) {
          angle = angle + Math.PI
        }
        const l = Math.sqrt((start.x - end.x) ** 2 + (start.y - end.y) ** 2)
        const div = document.querySelector(`#line-${node.id}-${child.id}`)
        if (div) {
          div.style.width = `${l}px`;
          div.style.position = 'absolute';
          div.style.left = `${start.x}px`;
          div.style.top = `${start.y}px`;
          div.style.transform = `rotate(${angle * 180 / Math.PI}deg)`;
          div.style.transformOrigin = 'left center';
        }
      })
    }
  },
  computed: {
    allLines() {
      return [...this.linePoints, ...this.waitAddLine]
    }
  }
}
</script>

<style lang="scss" scoped>
.map-container {
    min-width: calc(100% - 100px);
    min-height: calc(100% - 100px);
    box-sizing: border-box;
    margin: 50px;
    position: absolute;
    .node-item {
        position: absolute;
        z-index: 1;
        user-select: none;
        width: 80px;
        overflow: visible;
    }
}
::v-deep #line-container {
    .path-line {
        cursor: pointer;
        background: #2A3FCF;
        height: 2px;
        opacity: 0.5;
        overflow: visible;
        z-index: 99;
        .bar {
            opacity: 0;
            transition: opacity 0.5s;
            width: 16px;
            height: 16px;
            min-width: 16px;
            border-radius: 50%;
            background: red;
            position: relative;
            left: 8px;
        }
        .start-bar {
            background: #2A3FCF;
            left: -8px;
        }
        .el-icon-error {
            background: #ffffff;
            padding: 2px;
            font-size: 20px;
            display: none;
        }
    }
    .path-line:hover {
        opacity: 1;
        border-radius: 2px;
        height: 4px;
        .bar {
            opacity: 1;
        }
        .el-icon-error {
            display: block;
        }
    }
}
</style>